<!DOCTYPE html>
<html lang="zh_cn">
<head>
          <title>来玩魔王的咚</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charset="utf-8" />
        <!-- twitter card metadata -->
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="/images/mowang.png">
<meta name="twitter:site" content="">
<meta name="twitter:title" content="Python装饰器">
<meta name="twitter:description" content="<p>装饰器的设计原则和设计思路，逐步实现一个装饰器，讲明原理；装饰器的模板；装饰器的嵌套和执行顺序</p>">
        <!-- OG Tags -->
<meta property="og:url" content="/python-decorator.html"/>
<meta property="og:title" content="来玩魔王的咚 | Python装饰器" />
<meta property="og:description" content="<p>装饰器的设计原则和设计思路，逐步实现一个装饰器，讲明原理；装饰器的模板；装饰器的嵌套和执行顺序</p>" />
        <!-- favicon -->
        <link rel="icon" type="image/png" href="/images/mowang.png">
        <!-- moment.js for date formatting -->
        <script src="/theme/js/moment.js"></script>
        <!-- css -->
        <link rel="stylesheet" type="text/css" href="/theme/css/main.css" />
        <!-- 左边的menu，如果页面高度不够，就跟着滚动，否则文章分类显示不全 -->
        <link rel="stylesheet" type="text/css" href="/theme/css/mycss/menu.css" />
		<script>
			
                /*! grunt-grunticon Stylesheet Loader - v2.1.2 | https://github.com/filamentgroup/grunticon | (c) 2015 Scott Jehl, Filament Group, Inc. | MIT license. */
    
    (function(e){function t(t,n,r,o){"use strict";function a(){for(var e,n=0;u.length>n;n++)u[n].href&&u[n].href.indexOf(t)>-1&&(e=!0);e?i.media=r||"all":setTimeout(a)}var i=e.document.createElement("link"),l=n||e.document.getElementsByTagName("script")[0],u=e.document.styleSheets;return i.rel="stylesheet",i.href=t,i.media="only x",i.onload=o||null,l.parentNode.insertBefore(i,l),a(),i}var n=function(r,o){"use strict";if(r&&3===r.length){var a=e.navigator,i=e.Image,l=!(!document.createElementNS||!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect||!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1")||e.opera&&-1===a.userAgent.indexOf("Chrome")||-1!==a.userAgent.indexOf("Series40")),u=new i;u.onerror=function(){n.method="png",n.href=r[2],t(r[2])},u.onload=function(){var e=1===u.width&&1===u.height,a=r[e&&l?0:e?1:2];n.method=e&&l?"svg":e?"datapng":"png",n.href=a,t(a,null,null,o)},u.src="",document.documentElement.className+=" grunticon"}};n.loadCSS=t,e.grunticon=n})(this);(function(e,t){"use strict";var n=t.document,r="grunticon:",o=function(e){if(n.attachEvent?"complete"===n.readyState:"loading"!==n.readyState)e();else{var t=!1;n.addEventListener("readystatechange",function(){t||(t=!0,e())},!1)}},a=function(e){return t.document.querySelector('link[href$="'+e+'"]')},c=function(e){var t,n,o,a,c,i,u={};if(t=e.sheet,!t)return u;n=t.cssRules?t.cssRules:t.rules;for(var l=0;n.length>l;l++)o=n[l].cssText,a=r+n[l].selectorText,c=o.split(");")[0].match(/US\-ASCII\,([^"']+)/),c&&c[1]&&(i=decodeURIComponent(c[1]),u[a]=i);return u},i=function(e){var t,o,a;o="data-grunticon-embed";for(var c in e)if(a=c.slice(r.length),t=n.querySelectorAll(a+"["+o+"]"),t.length)for(var i=0;t.length>i;i++)t[i].innerHTML=e[c],t[i].style.backgroundImage="none",t[i].removeAttribute(o);return t},u=function(t){"svg"===e.method&&o(function(){i(c(a(e.href))),"function"==typeof t&&t()})};e.embedIcons=i,e.getCSS=a,e.getIcons=c,e.ready=o,e.svgLoadedCallback=u,e.embedSVG=u})(grunticon,this);
                
                grunticon(["/theme/css/icons.data.svg.css", "/theme/css/icons.data.png.css", "/theme/css/icons.fallback.css"]);
            </script>
        <noscript><link href="/theme/css/icons.fallback.css" rel="stylesheet"></noscript>
        <!-- menu toggle javascript -->
        <script type="text/javascript">
            document.addEventListener("DOMContentLoaded", initMenu);
            
            function initMenu(){
                var menu = document.getElementById("menu");
                var menulink = document.getElementById("menu-link");
                menulink.addEventListener("click", function toggleMenu(){
                        window.event.preventDefault();
                        menulink.classList.toggle('active');
                        menu.classList.toggle('active');              
                    });
            };
        </script>
        <!-- 不蒜子 -->
        <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

    <meta name="description" content="<p>装饰器的设计原则和设计思路，逐步实现一个装饰器，讲明原理；装饰器的模板；装饰器的嵌套和执行顺序</p>" />

    <meta name="tags" content="装饰器" />
  <!-- 替换部分base的样式，看文章时，要再宽一点，右边有很多空间可以撑开 -->
  <link rel="stylesheet" type="text/css" href="/theme/css/mycss/article.css" />

</head>
<body>
    <div role="banner" id="masthead">
        <header>
            <a href="/"><img src="/images/mowang.png" alt="McManus Logo"></a>
                <h1>来玩魔王的咚@骑士救兵</h1>
            <a href="#menu" id="menu-link">more stuff</a>
            <nav id="menu">
                <ul>
                        <li><a href="/tags">tags</a></li>
                            <li><a href="/category/cloud.html">Cloud</a></li>
                            <li><a href="/category/docker.html">Docker</a></li>
                            <li><a href="/category/go.html">Go</a></li>
                            <li><a href="/category/linux.html">Linux</a></li>
                            <li class="active"><a href="/category/python.html">Python</a></li>
                            <li><a href="/category/xue-xi-bi-ji.html">学习笔记</a></li>
                            <li><a href="/category/yun-wei-zi-dong-hua.html">运维自动化</a></li>
                </ul>
            </nav>
        </header>
    </div>
        <div class="page" role="main">
  <div class="article" role="article">
    <article>
        <footer>
            <a name="top"></a>
            <p>
              <time datetime=" 2020-07-02 14:00:00+08:00">
                <script>document.write(moment('2020-07-02 14:00:00+08:00').format('LL'));</script>
              </time>
              ~
              <time datetime=" 2020-07-02 14:00:00+08:00">
                <script>document.write(moment('2020-07-02 14:00:00+08:00').format('LL'));</script>
              </time>
            </p>
        </footer>
        <header>
          <h2>
            Python装饰器
          </h2>
        </header>
      <div class="content">
         <div class="toc">
<ul>
<li><a href="#shi-xian-zhuang-shi-qi">实现装饰器</a><ul>
<li><a href="#fu-jia-gong-neng">附加功能</a></li>
<li><a href="#bao-chi-diao-yong-fang-shi">保持调用方式</a></li>
<li><a href="#han-shu-can-shu-he-fan-hui-zhi">函数参数和返回值</a></li>
<li><a href="#dai-can-shu-de-zhuang-shi-qi">带参数的装饰器</a></li>
<li><a href="#shi-yong-fu-hao">使用@符号</a></li>
</ul>
</li>
<li><a href="#zhuang-shi-qi-mu-ban">装饰器模板</a></li>
<li><a href="#zhuang-shi-qi-qian-tao">装饰器嵌套</a><ul>
<li><a href="#ce-shi-zhi-xing-shun-xu">测试执行顺序</a></li>
<li><a href="#zhuang-shi-qi-wai-ceng-de-zhi-xing-shun-xu">装饰器外层的执行顺序</a></li>
<li><a href="#san-ceng-zhuang-shi-qi-de-zhi-xing-shun-xu">三层装饰器的执行顺序</a></li>
</ul>
</li>
</ul>
</div>
<p>通过对已有的函数进行修改，可以实现为函数增加功能。<br>
通过装饰器也可以为函数添加附加功能。并且使用装饰器还有额外的好处。<br>
首先，看下装饰器的2个原则：</p>
<ol>
<li>不改变被装饰函数的源代码</li>
<li>不改变被装饰函数的调用方式</li>
</ol>
<p>遵循了上面2个原则，我们可以在为函数添加附加功能的同时，不必去破坏已有的稳定的代码和代码调用方式。也就是说，不但函数自身的代码不用修改，原本所有对该函数的调用的代码也都不用修改。另外还有一个好处是易于回退。  </p>
<h3 id="shi-xian-zhuang-shi-qi"><a class="toclink" href="#shi-xian-zhuang-shi-qi">实现装饰器</a></h3>
<p>首先，来定义一个函数:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">time</span>

<span class="k">def</span> <span class="nf">sleep</span><span class="p">():</span>
    <span class="s2">&quot;运行后等待2秒&quot;</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;wait for 2 seconds&quot;</span><span class="p">)</span>
    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> 
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;END&quot;</span><span class="p">)</span>

<span class="n">sleep</span><span class="p">()</span>
</code></pre></div>

<p>函数的效果很简单。现在我希望为这个函数再附加一个功能，计算函数的运行时间。并且要做到不修改了sleep函数的源代码。  </p>
<h4 id="fu-jia-gong-neng"><a class="toclink" href="#fu-jia-gong-neng">附加功能</a></h4>
<p>在原有的基础上，再定义一个新的函数:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">time</span>

<span class="k">def</span> <span class="nf">sleep</span><span class="p">():</span>
    <span class="s2">&quot;运行后等待2秒&quot;</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;wait for 2 seconds&quot;</span><span class="p">)</span>
    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> 
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;END&quot;</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">run_time</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
    <span class="s2">&quot;计算参数的函数的运行时间&quot;</span>
    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
    <span class="n">func</span><span class="p">()</span>
    <span class="n">last</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;执行时间: </span><span class="si">%.2f</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="n">last</span><span class="p">)</span>

<span class="c1"># 这里函数的调用方式变了</span>
<span class="c1">#sleep()</span>
<span class="n">run_time</span><span class="p">(</span><span class="n">sleep</span><span class="p">)</span>
</code></pre></div>

<p>附加的功能实现了，并且sleep函数未做任何改动。但是调用的方式变了。  </p>
<h4 id="bao-chi-diao-yong-fang-shi"><a class="toclink" href="#bao-chi-diao-yong-fang-shi">保持调用方式</a></h4>
<p>在之前的基础上，我们给装饰器外面再套上一层函数。先看代码，下面再分析实现过程:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">time</span>

<span class="k">def</span> <span class="nf">sleep</span><span class="p">():</span>
    <span class="s2">&quot;运行后等待2秒&quot;</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;wait for 2 seconds&quot;</span><span class="p">)</span>
    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> 
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;END&quot;</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">run_time</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">wrapper</span><span class="p">():</span>
        <span class="s2">&quot;计算参数的函数的运行时间&quot;</span>
        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
        <span class="n">func</span><span class="p">()</span>
        <span class="n">last</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;执行时间: </span><span class="si">%.2f</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="n">last</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">wrapper</span>
<span class="n">sleep</span> <span class="o">=</span> <span class="n">run_time</span><span class="p">(</span><span class="n">sleep</span><span class="p">)</span>

<span class="n">sleep</span><span class="p">()</span>
</code></pre></div>

<p>原来的函数改名为wrapper，外面再嵌套一个函数，继续使用run_time这个名字。<br>
赋值语句<code>sleep = run_time(sleep)</code>执行后，sleep就是run_time函数执行后的返回值，也就是内层的wrapper函数。<br>
之后再执行sleep函数时，实际执行的是wrapper函数。而再wrapper函数中，原来的sleep函数以及附加的代码都执行了。  </p>
<p><strong>垃圾回收的问题</strong><br>
这里wrapper函数只是run_time函数内部的一个函数，是局部变量。在调用执行<code>run_time(sleep)</code>之后，函数运行结束，局部变量是会被释放的，之后会被垃圾回收机制回收。<br>
但是由于把结果赋值给了sleep，sleep指向的是wrapper函数的内存地址。由于这段内存被分配给了sleep，所以并不会被垃圾回收。依然会一直保留在内存中。  </p>
<p>另外，这里的wrapper函数还有一个<strong>闭包</strong>的概念。不过，暂时问题不大，就不在这里展开了。  </p>
<p>至此，符合装饰器2个原则的修改就实现了。  </p>
<h4 id="han-shu-can-shu-he-fan-hui-zhi"><a class="toclink" href="#han-shu-can-shu-he-fan-hui-zhi">函数参数和返回值</a></h4>
<p>上面的例子中，被装饰函数时没有参数的，另外也没有返回值。<br>
要适配函数有参数的情况，简单无脑的加上无固定参数就行了，具体是什么参数，具体有多少参数我们不用关心，全部传过去就行了。<br>
返回值的问题，调用被装饰函数时，先将返回值保存。在装饰器的最后，再return这个值。  </p>
<p>现在参数和返回值也都支持了:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">time</span>

<span class="k">def</span> <span class="nf">sleep</span><span class="p">(</span><span class="n">second</span><span class="p">):</span>
    <span class="s2">&quot;运行后等待指定的秒数&quot;</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;wait for </span><span class="si">{</span><span class="n">second</span><span class="si">}</span><span class="s2"> seconds&quot;</span><span class="p">)</span>
    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">second</span><span class="p">)</span> 
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;END&quot;</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">second</span> <span class="o">/</span> <span class="mi">60</span>

<span class="k">def</span> <span class="nf">run_time</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>  <span class="c1"># 使用无固定参数</span>
        <span class="s2">&quot;计算参数的函数的运行时间&quot;</span>
        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
        <span class="c1"># 调用函数，将参数全部传过去</span>
        <span class="c1"># 把函数执行的返回值，先保存好</span>
        <span class="n">res</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
        <span class="n">last</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;执行时间: </span><span class="si">%.2f</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="n">last</span><span class="p">)</span>
        <span class="c1"># 函数执行完，返回之前保存的返回值</span>
        <span class="k">return</span> <span class="n">res</span>
    <span class="k">return</span> <span class="n">wrapper</span>
<span class="n">sleep</span> <span class="o">=</span> <span class="n">run_time</span><span class="p">(</span><span class="n">sleep</span><span class="p">)</span>

<span class="n">minute</span> <span class="o">=</span> <span class="n">sleep</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;分钟数: </span><span class="si">{</span><span class="n">minute</span><span class="si">}</span><span class="s1"> 分钟&#39;</span><span class="p">)</span>
</code></pre></div>

<h4 id="dai-can-shu-de-zhuang-shi-qi"><a class="toclink" href="#dai-can-shu-de-zhuang-shi-qi">带参数的装饰器</a></h4>
<p>装饰器也是一个函数，作为函数，那么也可以使用参数。通过参数，可以使函数有更多的选项。<br>
之前的做法是为函数嵌套一层装饰器。现在为了让装饰器也能带上参数，需要再嵌套一层。<br>
这个就是可以带参数的3层装饰器:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">time</span>

<span class="k">def</span> <span class="nf">sleep</span><span class="p">(</span><span class="n">second</span><span class="p">):</span>
    <span class="s2">&quot;运行后等待指定的秒数&quot;</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;wait for </span><span class="si">{</span><span class="n">second</span><span class="si">}</span><span class="s2"> seconds&quot;</span><span class="p">)</span>
    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">second</span><span class="p">)</span> 
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;END&quot;</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">second</span> <span class="o">/</span> <span class="mi">60</span>

<span class="k">def</span> <span class="nf">run_time</span><span class="p">(</span><span class="n">unit</span><span class="o">=</span><span class="s1">&#39;s&#39;</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>  
        <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
            <span class="s2">&quot;计算参数的函数的运行时间&quot;</span>
            <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
            <span class="n">res</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
            <span class="n">last</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
            <span class="k">if</span> <span class="n">unit</span> <span class="o">==</span> <span class="s1">&#39;m&#39;</span><span class="p">:</span>
                <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;执行时间: </span><span class="si">%.2f</span><span class="s2"> 分钟&quot;</span> 
                <span class="o">%</span> <span class="p">(</span><span class="n">last</span> <span class="o">/</span> <span class="mi">60</span><span class="p">))</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;执行时间: </span><span class="si">%.2f</span><span class="s2"> 秒&quot;</span> <span class="o">%</span> <span class="n">last</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">res</span>
        <span class="k">return</span> <span class="n">wrapper</span>
    <span class="k">return</span> <span class="n">decorator</span>
<span class="c1">#sleep = run_time()(sleep)</span>
<span class="n">sleep</span> <span class="o">=</span> <span class="n">run_time</span><span class="p">(</span><span class="s1">&#39;m&#39;</span><span class="p">)(</span><span class="n">sleep</span><span class="p">)</span>

<span class="n">minute</span> <span class="o">=</span> <span class="n">sleep</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;分钟数: </span><span class="si">{</span><span class="n">minute</span><span class="si">}</span><span class="s1"> 分钟&#39;</span><span class="p">)</span>
</code></pre></div>

<p>把之前2层时候，外层的函数改名为decorator，继续用run_time作为函数名，再在外面包一层。这一层可以加上参数，然后内部的函数都可以直接使用。<br>
赋值语句后面是2个括号，每个括号表示的都是函数调用。run_time带括号执行，返回的是内部的decorator函数。后面在一个括号，就是表示decorator函数也要执行，sleep获取的是decorator函数的返回值，也就是里层的wrapper函数本身。在之后调用sleep函数的时候，实际调用的就是wrapper函数。  </p>
<h4 id="shi-yong-fu-hao"><a class="toclink" href="#shi-yong-fu-hao">使用@符号</a></h4>
<p>Python提供了@符号，使用@符号来替代原本在最终调用之前要执行的赋值语句。<br>
效果一样，虽然代码行数也没变。但是@语句使得代码更加简洁，并且位置被要求固定在被装饰函数之前，更加易于阅读。<br>
下面就是装饰器实际使用时的样子。<br>
二层装饰器:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">time</span>

<span class="k">def</span> <span class="nf">run_time</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>  <span class="c1"># 使用无固定参数</span>
        <span class="s2">&quot;计算参数的函数的运行时间&quot;</span>
        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
        <span class="c1"># 调用函数，将参数全部传过去</span>
        <span class="c1"># 把函数执行的返回值，先保存好</span>
        <span class="n">res</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
        <span class="n">last</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;执行时间: </span><span class="si">%.2f</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="n">last</span><span class="p">)</span>
        <span class="c1"># 函数执行完，返回之前保存的返回值</span>
        <span class="k">return</span> <span class="n">res</span>
    <span class="k">return</span> <span class="n">wrapper</span>

<span class="nd">@run_time</span>
<span class="k">def</span> <span class="nf">sleep</span><span class="p">(</span><span class="n">second</span><span class="p">):</span>
    <span class="s2">&quot;运行后等待指定的秒数&quot;</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;wait for </span><span class="si">{</span><span class="n">second</span><span class="si">}</span><span class="s2"> seconds&quot;</span><span class="p">)</span>
    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">second</span><span class="p">)</span> 
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;END&quot;</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">second</span> <span class="o">/</span> <span class="mi">60</span>

<span class="n">minute</span> <span class="o">=</span> <span class="n">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;分钟数: </span><span class="si">{</span><span class="n">minute</span><span class="si">}</span><span class="s1"> 分钟&#39;</span><span class="p">)</span>
</code></pre></div>

<p>三层装饰器:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">time</span>

<span class="k">def</span> <span class="nf">run_time</span><span class="p">(</span><span class="n">unit</span><span class="o">=</span><span class="s1">&#39;s&#39;</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>  
        <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
            <span class="s2">&quot;计算参数的函数的运行时间&quot;</span>
            <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
            <span class="n">res</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
            <span class="n">last</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
            <span class="k">if</span> <span class="n">unit</span> <span class="o">==</span> <span class="s1">&#39;m&#39;</span><span class="p">:</span>
                <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;执行时间: </span><span class="si">%.2f</span><span class="s2"> 分钟&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">last</span> <span class="o">/</span> <span class="mi">60</span><span class="p">))</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;执行时间: </span><span class="si">%.2f</span><span class="s2"> 秒&quot;</span> <span class="o">%</span> <span class="n">last</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">res</span>
        <span class="k">return</span> <span class="n">wrapper</span>
    <span class="k">return</span> <span class="n">decorator</span>

<span class="nd">@run_time</span><span class="p">(</span><span class="s1">&#39;m&#39;</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">sleep</span><span class="p">(</span><span class="n">second</span><span class="p">):</span>
    <span class="s2">&quot;运行后等待指定的秒数&quot;</span>
    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;wait for </span><span class="si">{</span><span class="n">second</span><span class="si">}</span><span class="s2"> seconds&quot;</span><span class="p">)</span>
    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">second</span><span class="p">)</span> 
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;END&quot;</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">second</span> <span class="o">/</span> <span class="mi">60</span>

<span class="n">minute</span> <span class="o">=</span> <span class="n">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;分钟数: </span><span class="si">{</span><span class="n">minute</span><span class="si">}</span><span class="s1"> 分钟&#39;</span><span class="p">)</span>
</code></pre></div>

<h3 id="zhuang-shi-qi-mu-ban"><a class="toclink" href="#zhuang-shi-qi-mu-ban">装饰器模板</a></h3>
<p>下面是装饰器的模板，就算没完全理解，也能直接套用:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">func_name_2</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
    <span class="sd">&quot;&quot;&quot;二层装饰器模板&quot;&quot;&quot;</span>
    <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
        <span class="c1"># 添加调用函数前，要执行的代码</span>
        <span class="n">res</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
        <span class="c1"># 添加调用函数后，要执行的代码</span>
        <span class="k">return</span> <span class="n">res</span>
    <span class="k">return</span> <span class="n">wrapper</span>

<span class="k">def</span> <span class="nf">func_name_3</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>  <span class="c1"># 括号内可以随意加参数</span>
    <span class="sd">&quot;&quot;&quot;三层装饰器模板&quot;&quot;&quot;</span>
    <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">func</span><span class="p">):</span> 
        <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
            <span class="c1"># 添加调用函数前，要执行的代码</span>
            <span class="n">res</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
            <span class="c1"># 添加调用函数后，要执行的代码</span>
            <span class="k">return</span> <span class="n">res</span>
        <span class="k">return</span> <span class="n">wrapper</span>
    <span class="k">return</span> <span class="n">decorator</span>
</code></pre></div>

<p>简单的理解：通过装饰器可以在执行函数之前先执行一段代码，在执行函数之后也可以再执行一段代码。<br>
这就是装饰器一个基本的使用场景。实际还会有其他高级用法，这个模板则是针对在这种场景下使用的。  </p>
<p>三层装饰器最外层的参数:</p>
<ul>
<li>不要和内部的参数重名</li>
<li>内部的任何位置都可以引用，decorator，wrapper 都能用</li>
<li>外层的参数不要出现在等号左边</li>
</ul>
<p><em>这里涉及到闭包的概念。详细情况还是再了解下闭包吧。在哪之前，就简单的避免在赋值语句左边出现就好了。</em>    </p>
<h3 id="zhuang-shi-qi-qian-tao"><a class="toclink" href="#zhuang-shi-qi-qian-tao">装饰器嵌套</a></h3>
<p>之前的示例中，只使用了一个装饰器。有的时候，需要多个装饰器叠加使用。<br>
比如已经用装饰器为函数附加了一个功能了，之后再要附加一个功能，就可以再写一个装饰器，再附加上去:</p>
<div class="highlight"><pre><span></span><code><span class="nd">@funA</span>
<span class="nd">@funB</span>
<span class="nd">@funC</span>
<span class="k">def</span> <span class="nf">fun</span><span class="p">():</span>
    <span class="k">pass</span>
</code></pre></div>

<p><em>这里先给出结论，之后是验证的代码。</em><br>
<strong>效果</strong>：按顺序从上往下依次生效。<br>
比如，之前一直用的run_time，用来计算函数执行时间的装饰器，就需要放在最上面。如果放到下面，则之前的装饰器执行的时间都不会计算在内。  </p>
<p>上面的代码等效于下面这样:</p>
<div class="highlight"><pre><span></span><code><span class="n">fun</span> <span class="o">=</span> <span class="n">funA</span><span class="p">(</span> <span class="n">funB</span> <span class="p">(</span> <span class="n">funC</span> <span class="p">(</span> <span class="n">fun</span> <span class="p">)</span> <span class="p">)</span> <span class="p">)</span>
</code></pre></div>

<h4 id="ce-shi-zhi-xing-shun-xu"><a class="toclink" href="#ce-shi-zhi-xing-shun-xu">测试执行顺序</a></h4>
<p>具体怎么样，还是实际试一下看看，测试代码：</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">funA</span><span class="p">(</span><span class="n">f</span><span class="p">):</span>
    <span class="c1">#print(&quot;装饰器外层: funA&quot;)</span>
    <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;装饰器内层: funA()，被装饰函数执行前&quot;</span><span class="p">)</span>
        <span class="n">res</span> <span class="o">=</span> <span class="n">f</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;装饰器内层: funA()，被装饰函数执行后&quot;</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">res</span>
    <span class="k">return</span> <span class="n">wrapper</span>

<span class="k">def</span> <span class="nf">funB</span><span class="p">(</span><span class="n">f</span><span class="p">):</span>
    <span class="c1">#print(&quot;装饰器外层: funB&quot;)</span>
    <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;装饰器内层: funB()，被装饰函数执行前&quot;</span><span class="p">)</span>
        <span class="n">res</span> <span class="o">=</span> <span class="n">f</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;装饰器内层: funB()，被装饰函数执行后&quot;</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">res</span>
    <span class="k">return</span> <span class="n">wrapper</span>

<span class="k">def</span> <span class="nf">funC</span><span class="p">(</span><span class="n">f</span><span class="p">):</span>
    <span class="c1">#print(&quot;装饰器外层: funC&quot;)</span>
    <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;装饰器内层: funC()，被装饰函数执行前&quot;</span><span class="p">)</span>
        <span class="n">res</span> <span class="o">=</span> <span class="n">f</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;装饰器内层: funC()，被装饰函数执行后&quot;</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">res</span>
    <span class="k">return</span> <span class="n">wrapper</span>

<span class="nd">@funA</span>
<span class="nd">@funB</span>
<span class="nd">@funC</span>
<span class="k">def</span> <span class="nf">fun</span><span class="p">():</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;执行: fun()&#39;</span><span class="p">)</span>
<span class="c1"># 把上面的装饰器注释掉，放开下面的赋值语句，效果一样</span>
<span class="c1">#fun = funA(funB(funC(fun)))</span>

<span class="n">fun</span><span class="p">()</span>
</code></pre></div>

<p>执行后的输出为:</p>
<div class="highlight"><pre><span></span><code>装饰器内层: funA()，被装饰函数执行前
装饰器内层: funB()，被装饰函数执行前
装饰器内层: funC()，被装饰函数执行前
执行: fun()
装饰器内层: funC()，被装饰函数执行后
装饰器内层: funB()，被装饰函数执行后
装饰器内层: funA()，被装饰函数执行后
</code></pre></div>

<p>可以看到，多个装饰器是从上往下，依次执行的。  </p>
<h4 id="zhuang-shi-qi-wai-ceng-de-zhi-xing-shun-xu"><a class="toclink" href="#zhuang-shi-qi-wai-ceng-de-zhi-xing-shun-xu">装饰器外层的执行顺序</a></h4>
<p>上面的示例代码，每个装饰器外层还有一个print语句。去掉注释之后执行:</p>
<div class="highlight"><pre><span></span><code>装饰器外层: funC
装饰器外层: funB
装饰器外层: funA
装饰器内层: funA()，被装饰函数执行前
装饰器内层: funB()，被装饰函数执行前
装饰器内层: funC()，被装饰函数执行前
执行: fun()
装饰器内层: funC()，被装饰函数执行后
装饰器内层: funB()，被装饰函数执行后
装饰器内层: funA()，被装饰函数执行后
</code></pre></div>

<p>语句<code>@funA</code>，是有一个函数调用的动作的，函数funA会在这个位置就被调用执行，这次在函数开始添加了打印语句，可以看到效果。<br>
外层的执行顺序是从最靠近被装饰函数的装饰器开始返回结果的。<em>函数其实还是按顺序从上往下被调用的</em>  </p>
<h4 id="san-ceng-zhuang-shi-qi-de-zhi-xing-shun-xu"><a class="toclink" href="#san-ceng-zhuang-shi-qi-de-zhi-xing-shun-xu">三层装饰器的执行顺序</a></h4>
<p>对于三层装饰器，@语句后面是有括号的。本身@语句会把在外层的函数执行，后面的括号会把返回的中间层的函数直接调用执行。<br>
所以三层函数的外面2层，都会在@语句的位置立即执行。执行的顺序是先执行外层函数的全部代码，再执行中间层的全部代码。<br>
测试代码:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">funD</span><span class="p">():</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;装饰器外层: funD 开头&quot;</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">func</span><span class="p">):</span> 
        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;装饰器中层: funD 开头&quot;</span><span class="p">)</span>
        <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
            <span class="c1"># 添加调用函数前，要执行的代码</span>
            <span class="n">res</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">arg</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
            <span class="c1"># 添加调用函数后，要执行的代码</span>
            <span class="k">return</span> <span class="n">res</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;装饰器中层: funD 结尾&quot;</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">wrapper</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;装饰器外层: funD 结尾&quot;</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">decorator</span>

<span class="nd">@funD</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">fun</span><span class="p">():</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;执行: fun()&#39;</span><span class="p">)</span>

<span class="n">fun</span><span class="p">()</span>
</code></pre></div>

<p>执行后的结果:</p>
<div class="highlight"><pre><span></span><code>装饰器外层: funD 开头
装饰器外层: funD 结尾
装饰器中层: funD 开头
装饰器中层: funD 结尾
执行: fun()
</code></pre></div>

<p>最后，嵌套的情况，就和2层装饰器是一样的。外面两层先在@的位置调用，从下往上返回结果。内部的代码则是从上往下顺序执行。  </p>
      </div>
      <div class="back-to-top">
        <a href="/">HOME</a>
        <a href="#top">TOP</a>
      </div>
    </article>
  </div>
<!-- end article -->
<!-- 页面往下滚动一段之后才会显示TOC -->
<script>
  window.onscroll = function() {
    var tocbox = document.getElementsByClassName('toc')[0];
    var osTop = document.documentElement.scrollTop || document.body.scrollTop;
    var osWidth = document.documentElement.scrollWidth || document.body.scrollWidth;
    // console.log(osTop)
    if (osTop>300 && osWidth>865) {
      tocbox.style.display = "block"
    }
    if (osTop<300 || osWidth<865) {
      tocbox.style.display = "none"
    }
  }
</script>
                <footer>
                    <div class="icons">
                    </div>
                    <span id="busuanzi_container_page_pv" style="padding: 10px">本文阅读量<span id="busuanzi_value_page_pv"></span>次</span>
                    <span id="busuanzi_container_site_pv" style="padding: 10px">本站总访问量<span id="busuanzi_value_site_pv"></span>次</span>
                    <span id="busuanzi_container_site_uv" style="padding: 10px">本站总访客数<span id="busuanzi_value_site_uv"></span>人</span>
                    <p>© <script>document.write(moment().format('YYYY'));</script> 749B</p>
                </footer>
        </div>
</body>
</html>